package com.kaihong.template.ble.operation.impl;

import ohos.app.Context;
import ohos.bluetooth.ble.BlePeripheralDevice;

import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;

import com.kaihong.template.ble.operation.BleOperator;
import com.kaihong.template.ble.operation.OptionalConsumer;
import com.kaihong.template.ble.operation.callback.adapter.BluetoothAdapterStateChangeCallback;
import com.kaihong.template.ble.operation.callback.adapter.BluetoothDeviceFoundCallback;
import com.kaihong.template.ble.operation.callback.gatt.BleCharacteristicValueChangeCallback;
import com.kaihong.template.ble.operation.callback.gatt.BleConnectionStateChangeCallback;
import com.kaihong.template.ble.operation.callback.gatt.BleServicesDiscoveredCallback;
import com.kaihong.template.ble.operation.callback.gatt.EnableNotifyIndicateCallback;
import com.kaihong.template.ble.operation.callback.gatt.ReadBleCharacteristicValueCallback;
import com.kaihong.template.ble.operation.callback.gatt.ReadBleDescriptorValueCallback;
import com.kaihong.template.ble.operation.callback.gatt.WriteBleCharacteristicValueCallback;
import com.kaihong.template.ble.operation.callback.gatt.WriteBleDescriptorValueCallback;
import com.kaihong.template.ble.operation.model.DescriptorData;
import com.kaihong.template.opinterface.OperatorCode;
import com.kaihong.template.utils.Logger;

/**
 * BleOperator impl
 */
public class BleOperatorImpl implements BleOperator {
    private static final String TAG = "BleOperatorImpl";

    private static volatile BleOperator sInstance;

    private final BleCentralOperator bleCentralOperator;

    private Optional<BleGattOperator> bleGattOperator;

    private String deviceId;

    private BleServicesDiscoveredCallback bleServicesDiscoveredCallback;

    private BleConnectionStateChangeCallback bleConnectionStateChangeCallback;

    private BleCharacteristicValueChangeCallback bleCharacteristicValueChangeCallback;

    private EnableNotifyIndicateCallback enableNotifyIndicateCallback;

    private BleOperatorImpl(Context context) {
        bleGattOperator = Optional.empty();
        bleCentralOperator = BleCentralOperator.getInstance(context);
    }

    /**
     * create single instance
     *
     * @param context context
     * @return single instance
     */
    public static BleOperator getInstance(Context context) {
        Logger.i(TAG, "BleOperator getInstance");
        if (sInstance == null) {
            synchronized (BleOperatorImpl.class) {
                if (sInstance == null) {
                    sInstance = new BleOperatorImpl(context);
                }
            }
        }
        return sInstance;
    }

    @Override
    public String openBluetoothAdapter() {
        Logger.i(TAG, "openBluetoothAdapter entry");
        int resultCode = bleCentralOperator.openBluetoothAdapter();
        return String.valueOf(resultCode);
    }

    @Override
    public String closeBluetoothAdapter() {
        Logger.i(TAG, "closeBluetoothAdapter entry");
        int resultCode = bleCentralOperator.closeBluetoothAdapter();
        return String.valueOf(resultCode);
    }

    @Override
    public boolean getBluetoothAdapterState() {
        Logger.i(TAG, "getBluetoothAdapterState entry");
        return bleCentralOperator.getBluetoothAdapterState();
    }

    @Override
    public void onBluetoothAdapterStateChange(BluetoothAdapterStateChangeCallback stateChangeCallback) {
        bleCentralOperator.onBluetoothAdapterStateChange(stateChangeCallback);
    }

    @Override
    public String getDeviceId() {
        return deviceId;
    }

    @Override
    public String startBluetoothDevicesDiscovery(UUID[] services, String mac, boolean isAllowDuplicatesKey, int interval) {
        Logger.i(TAG, "startBluetoothDevicesDiscovery services=" + Arrays.toString(services));
        int resultCode = bleCentralOperator.startBluetoothDevicesDiscovery(services, mac, isAllowDuplicatesKey, interval);
        return String.valueOf(resultCode);
    }

    @Override
    public String stopBluetoothDevicesDiscovery() {
        int resultCode = bleCentralOperator.stopBluetoothDevicesDiscovery();
        return String.valueOf(resultCode);
    }

    @Override
    public void onBluetoothDeviceFound(BluetoothDeviceFoundCallback deviceFoundCallback) {
        Logger.i(TAG, "onBluetoothDeviceFound entry");
        bleCentralOperator.onBluetoothDeviceFound(deviceFoundCallback);
    }

    @Override
    public String createBleConnection(String deviceId) {
        this.deviceId = deviceId;
        BlePeripheralDevice device = BlePeripheralDevice.createInstance(deviceId);
        bleGattOperator = Optional.of(BleGattOperator.getInstance(device, bleCharacteristicValueChangeCallback,
                bleConnectionStateChangeCallback, bleServicesDiscoveredCallback, enableNotifyIndicateCallback));
        return bleGattOperator.map(gattOperator -> String.valueOf(gattOperator.createBleConnection(this.deviceId)))
                .orElse(String.valueOf(OperatorCode.ERROR_CODE_COMMON_ERR));
    }

    @Override
    public String closeBleConnection(String deviceId) {
        return bleGattOperator.map(gattOperator -> String.valueOf(gattOperator.closeBleConnection(this.deviceId)))
                .orElse(String.valueOf(OperatorCode.ERROR_CODE_COMMON_ERR));
    }

    @Override
    public void onBleConnectionStateChange(BleConnectionStateChangeCallback connectionStateChangeCallback) {
        OptionalConsumer.of(bleGattOperator).ifPresent(operator -> {
            Logger.i(TAG, "onBLEConnectionStateChange operator present.");
            operator.onBleConnectionStateChange(connectionStateChangeCallback);
        }).ifNotPresent(() -> {
            Logger.i(TAG, "onBLEConnectionStateChange operator not present.");
            this.bleConnectionStateChangeCallback = connectionStateChangeCallback;
        });
    }

    @Override
    public void onBleServicesDiscovered(BleServicesDiscoveredCallback servicesDiscoveredCallback) {
        Logger.i(TAG, "onBleServiceDiscovered isPresent = " + bleGattOperator.isPresent());
        OptionalConsumer.of(bleGattOperator)
                .ifPresent(operator -> operator.onBleServicesDiscovered(servicesDiscoveredCallback))
                .ifNotPresent(() -> this.bleServicesDiscoveredCallback = servicesDiscoveredCallback);
    }

    @Override
    public void readBleCharacteristicValue(String deviceId, String serviceId, String characteristicId,
                                           ReadBleCharacteristicValueCallback readBleCharacteristicValueCallback) {
        bleGattOperator.ifPresent(operator -> operator.readBleCharacteristicValue(deviceId, serviceId, characteristicId,
                readBleCharacteristicValueCallback));
    }

    @Override
    public void writeBleCharacteristicValue(DescriptorData descriptorData,
                                            WriteBleCharacteristicValueCallback writeCharacteristicCallback) {
        bleGattOperator
                .ifPresent(operator -> operator.writeBleCharacteristicValue(descriptorData, writeCharacteristicCallback));
    }

    @Override
    public String notifyBleCharacteristicValueChange(String deviceId, String serviceId, String characteristicId,
                                                     boolean isState) {
        Logger.i(TAG, "notify in operator impl, mac addr:" + deviceId);
        return String.valueOf(bleGattOperator
                .map(
                        operator -> operator.notifyBleCharacteristicValueChange(deviceId, serviceId, characteristicId, isState))
                .orElse(OperatorCode.ERROR_CODE_COMMON_ERR));
    }

    @Override
    public void onEnableNotifyIndicate(EnableNotifyIndicateCallback enableNotifyIndicateCallback) {
        OptionalConsumer.of(bleGattOperator)
                .ifPresent(operator -> operator.onEnableNotifyIndicate(enableNotifyIndicateCallback))
                .ifNotPresent(() -> this.enableNotifyIndicateCallback = enableNotifyIndicateCallback);
    }

    @Override
    public String readBleDescriptorValue(String deviceId, String serviceId, String characteristicId,
                                         String descriptorId, ReadBleDescriptorValueCallback readBleDescriptorValueCallback) {
        return String
                .valueOf(
                        bleGattOperator
                                .map(operator -> operator.readBleDescriptorValue(deviceId, serviceId, characteristicId,
                                        descriptorId, readBleDescriptorValueCallback))
                                .orElse(OperatorCode.ERROR_CODE_COMMON_ERR));
    }

    @Override
    public String writeBleDescriptorValue(DescriptorData descriptorData,
                                          WriteBleDescriptorValueCallback writeDescriptorCallback) {
        return String.valueOf(
                bleGattOperator.map(operator -> operator.writeBleDescriptorValue(descriptorData, writeDescriptorCallback))
                        .orElse(OperatorCode.ERROR_CODE_COMMON_ERR));
    }

    @Override
    public void setEnableIndication(boolean isEnableIndication) {
        bleGattOperator.ifPresent(operator -> operator.setEnableIndication(isEnableIndication));
    }

    @Override
    public void onBleCharacteristicValueChange(BleCharacteristicValueChangeCallback characteristicValueChangeCallback) {
        OptionalConsumer.of(bleGattOperator)
                .ifPresent(operator -> operator.onBleCharacteristicValueChange(characteristicValueChangeCallback))
                .ifNotPresent(() -> this.bleCharacteristicValueChangeCallback = characteristicValueChangeCallback);
    }

    @Override
    public boolean getBleConnectionState(String deviceId) {
        if (deviceId == null || deviceId.isEmpty()) {
            return false;
        }
        return bleCentralOperator.getBleConnectionState(deviceId);

    }
}
